home *** CD-ROM | disk | FTP | other *** search
/ Skunkware 98 / Skunkware 98.iso / osr5 / sco / scripts / lastlogin < prev    next >
Encoding:
AWK Script  |  1997-08-26  |  75.1 KB  |  2,146 lines

  1. #!/usr/local/bin/gawk -f
  2. # Use gawk for strftime()
  3. # @(#) lastlogin.gawk 3.3 97/04/20
  4. # 93/04/20 john h. dubois iii (john@armory.com)
  5. # 93/05/12 Deal with unstatable and nonexistant .lastlogin files.
  6. # 94/01/14 Added all options.
  7. # 94/02/08 Added u option and check for bogus dates; use mod time instead of
  8. #          access time on .lastlogin; use name of user running program if
  9. #          no names given.
  10. # 94/02/09 Added l option, and explicit exit 0 from stat command to avoid
  11. #          gawk error message.
  12. # 95/01/21 Added o options.
  13. # 95/03/05 Improved help description of -o.
  14. # 95/05/13 Added x option.
  15. # 95/05/25 Don't print empty info lines for users who have never logged in.
  16. # 95/07/31 Deal with new stat error messages
  17. # 95/08/10 Fixed s option so it works correctly.
  18. # 95/09/09 Added rt options.
  19. # 95/12/30 Added H option, and use of rcfile
  20. # 96/01/17 Added .zhistory as history file for zsh
  21. # 96/01/20 Also use rcfile in UHOME.
  22. # 96/01/26 Do not print header if no reports will be printed.
  23. # 96/05/24 Added n option.
  24. # 96/06/03 Added Ac options.
  25. # 96/06/24 Added N option.
  26. # 96/06/29 Added gCe options.
  27. # 96/09/02 Print estimated account creation time for never-logged-in accounts.
  28. # 97/02/01 Use PWSearch(); added options for its facilities
  29. # 97/02/17 Added popmail check.
  30. # 97/04/16 Added LdP options.
  31. # 97/04/20 Added SEz options.
  32.  
  33. # Globals Attempt, Suc, and Logout are each either left null or set to a 
  34. # sequence number in [1..3]
  35. BEGIN {
  36.     Name = "lastlogin"
  37.     NewFormat = "%a %b %d %H:%M"
  38.     OldFormat = "%a %b %d %Y"
  39.     NewShortFormat = "%b %d %H:%M"
  40.     OldShortFormat = "%b %d %Y"
  41.     rcFile = ".lastloginrc"
  42.     Usage = "Usage:\n" \
  43.     Name " [-aAcCeEgGhHilMnNoOpRsTLz] [-r<dir>] [-t<time-format>] [user ...]"
  44.     # f is pseudo-opt for REPORTS var.
  45.     # Unused: bBDIjJkKOqQTuUVwXyYZ
  46.     ARGC = Opts(Name,Usage,"cCdef:GHnPsSt:aAEghilLMNopr:Rxz",0,
  47.     "~/" rcFile ":$UHOME/" rcFile, "CASE,AUTOCASE,DATE,EXACT,REPORTS,"\
  48.     "AUTOGCOS,HEADER,NAME,PERIOD,SORT,NONEVER,TIMEFORMAT",
  49.     0,"z",0,"","laop,M,N;i,A,L")
  50.     if ("h" in Options) {
  51.     printf \
  52. "%s: show last login times of users.\n"\
  53. "%s\n"\
  54. "If no user names are given, the invoking user's times are reported.\n"\
  55. "Options:\n"\
  56. "Some of the following options can also be set by assigning values to\n"\
  57. "variables in a configuration file named %s, which is searched for in\n"\
  58. "the invoking user's home directory and in the directory specified by the\n"\
  59. "environment variable UHOME, if it is set (if both files exist, values set\n"\
  60. "in the former take precedence).  Variables are assigned to with the\n"\
  61. "syntax:  varname=value  or in the case of flags, by simply putting the\n"\
  62. "indicated variable name in the file without a value.\n"\
  63. "Report type selection options:\n"\
  64. "-l: Report last successful login (the default).\n"\
  65. "-a: Report time of last login attempt, whether successful or not.\n"\
  66. "-o: Report time of last logout.  This option only works for users whose\n"\
  67. "    login shell maintains a command history between logins.  A - will be\n"\
  68. "    printed instead of a logout time for users of other shells, or if the\n"\
  69. "    user's history file is not being maintained.  The -o report will give\n"\
  70. "    an incorrect result for currently logged in users of shells that\n"\
  71. "    continuously write to their history files.\n"\
  72. "-p: Report last popmail access.  This depends on the POP daemon touching\n"\
  73. "    a file named .<username>.pop in /usr/spool/mail whenever a popmail\n"\
  74. "    access is done.\n"\
  75. "    -l, -a, -o, and -p can be used together to get multiple reports.\n"\
  76. "    The order of times printed on each line will be the same as the order\n"\
  77. "    the options are given in.\n"\
  78. "If all four reports are requested, an abbreviated output format is used.\n"\
  79. "The [laop] options may be set in the rcfile by assigning them to the\n"\
  80. "REPORTS variable, e.g.: REPORTS=lo\n"\
  81. "-M: Consistency check: Among the selected users, report (only) those who\n"\
  82. "    have a logout file of the type described above, but do not have a\n"\
  83. "    .lastlogin file.  Use with -A to find all such users.\n"\
  84. "-N: Print only the names of users who have never logged in, along with an\n"\
  85. "    estimation of the dates that the accounts were created, determined by\n"\
  86. "    checking the creation times of the accounts' shell startup files.\n"\
  87. "General options:\n"\
  88. "-h: Print this help.\n"\
  89. "-H: Print a header.  Variable: HEADER\n"\
  90. "-r<dir>: Specify an alternate root filesystem.  The /etc/passwd file and\n"\
  91. "    home directories are searched for relative to <dir>.\n"\
  92. "-z: Do not read configuration file.\n"\
  93. "Output formatting options:\n"\
  94. "-s: Sort output by the first time printed for each line.  Variable: SORT\n"\
  95. "-n: Include each user's \"real name\" in the output.  Variable: NAME\n"\
  96. "-E: Print only the most recent time among the reports requested.\n"\
  97. "-t<time-format>: Use strftime(S)-style time format string <time-format>\n"\
  98. "    to format all times.  If an empty string is passed, the epoch time (in\n"\
  99. "    seconds) is used.  The default is to use '%s' for times up to\n"\
  100. "    half a year ago, and '%s' for times before that.  If all four\n"\
  101. "    reports are requested and the COLUMNS environment variable is not set\n"\
  102. "    to a value >= 97, the day of the week is omitted.  Variable: TIMEFORMAT\n"\
  103. "-d: Show each time as a date and time (this is the default).  (DATE)\n"\
  104. "-P: Show each time as an \"age\" - the period that has elapsed between the\n"\
  105. "    time and the current time.  Ages are printed as: [<days>d] hh:mm\n"\
  106. "    (PERIOD)  Both -d and -P may be given; two lines will be printed.\n"\
  107. "-S: Do not print anything for users who have never logged in.  (NONEVER)\n"\
  108. "User selection options:\n"\
  109. "-A: Report on all \"real\" users (those who have a shell in /etc/shells).\n"\
  110. "-L: Report on all users currently logged in.\n"\
  111. "-i: Read names of users to report on from the standard input.\n"\
  112. "    Multiple lines of input and multiple names per line may be given.\n"\
  113. "User name matching options:\n"\
  114. "-g: Search for names in the GCOS (\"real name\") field instead of the\n"\
  115. "    account name field.  The search is not case sensitive.  Unlike\n"\
  116. "    searches by account name, the name need not match the entire field.\n"\
  117. "    The words of each name given must occur in the same order in a GCOS\n"\
  118. "    field in order for a match to occur.  Multiple password file entries\n"\
  119. "    may be matched by one name.  If multiple words of a name are given,\n"\
  120. "    they must be quoted to form a single argument.  Example:\n"\
  121. "    %s -g 'john somebody'\n"\
  122. "-R: Treat the given names as unanchored regular expressions.  Names are\n"\
  123. "    compared to the same field that would be searched without -R.  With\n"\
  124. "    this option, the meaning of -e is changed to mean that the regular\n"\
  125. "    expression should be anchored at the start and end, and the meaning\n"\
  126. "    of -c is changed to mean that the match should be case sensitive.\n"\
  127. "-G: If a name is not found as a user name, search for it in the GCOS field\n"\
  128. "    as though -g had been given.  (AUTOGCOS)\n"\
  129. "-e: An exact match of the full GCOS field is required.  (EXACT)\n"\
  130. "-c: Matches against the GCOS field are made case sensitive. (CASE)\n"\
  131. "-C: Like -c, except that a match is only case sensitive for names that\n"\
  132. "    are given with some characters in upper case.  (AUTOCASE)\n"\
  133. "If -e, -c, or -C is given on the command line and -R is not given, the -g\n"\
  134. "option is turned on.  If the e, c, or C option is turn on by puttings its\n"\
  135. "variable in a config file, they only have an effect if -R or -g is given.\n",
  136.     Name,Usage,rcFile,Name,NewFormat,OldFormat
  137.     exit 0
  138.     }
  139.  
  140.     autoGCOS = "G" in Options
  141.     CaseSensitive = "c" in Options
  142.     Full = "e" in Options
  143.     regex = "R" in Options
  144.     autoCase = "C" in Options
  145.     GCOS = ("g" in Options) || !regex && (CmdLineOpt(Options,"e") || \
  146.     CmdLineOpt(Options,"C") || CmdLineOpt(Options,"c"))
  147.  
  148.     mostRecent = "E" in Options
  149.     Consistency = "M" in Options
  150.     AllUsers = "A" in Options
  151.     LoggedInUsers = "L" in Options
  152.     ReadInput = "i" in Options
  153.     Header = "H" in Options
  154.     Sort = "s" in Options
  155.     Debug = "x" in Options
  156.     neverOnly = "N" in Options
  157.     printNever = !("S" in Options)
  158.     PrintPeriod = "P" in Options
  159.     PrintDate = !PrintPeriod || ("d" in Options)
  160.     if ("r" in Options) {
  161.     ROOT = Options["r"]
  162.     ReadPasswd(ROOT "/etc/passwd")
  163.     }
  164.     else
  165.     ReadPasswd()    # set PW_ names.
  166.     Field = GCOS ? PW_GCOS : PW_NAME
  167.  
  168.     # Get sequence number for each option
  169.     # All of this is to assign Attempt, Suc, and Logout either nothing (if
  170.     # their option was not given), or a number 1-4 indicating what order
  171.     # they were given in.  The problem is that there will be gaps in their
  172.     # sequence numbers if other options are given with them.
  173.     # Set array values for use by H option.
  174.     InitArr(QueryOpts,"a:l:o:p:r",
  175.     "Last Attempt:Last Login:Last Logout:POP Access:Most Recent",":")
  176.     if (neverOnly)
  177.     ;
  178.     else if (!("a" in Options || "l" in Options || "o" in Options || \
  179.     "p" in Options) && "f" in Options) {
  180.     NumQuery = SplitS(Options["f"],Query)
  181.     for (Q = 1; Q <= NumQuery; Q++) {
  182.         if (Debug)
  183.         printf "Processing option %s of REPORTS string: %s\n",Q,
  184.         Query[Q] > "/dev/stderr"
  185.         if (!(Query[Q] in QueryOpts)) {
  186.         printf "Unknown query type '%s' given with REPORTS var.\n",
  187.         Q > "/dev/stderr"
  188.         exit 1
  189.         }
  190.         Query[Query[Q]] = Q
  191.     }
  192.     }
  193.     else {
  194.     # For each query option letter...
  195.     for (Q in QueryOpts)
  196.         # If the option was given on the command line...
  197.         if ((Q,"num",1) in Options)
  198.         # Set Query[option-letter] to its relative pos on cmd line
  199.         Query[Options[Q,"num",1]] = Q
  200.     # Sort the options to get a map that puts them in their cmd line order
  201.     NumQuery = qsortByArbIndex(Query,k)
  202.     # Set Query[option-letter] to an integer giving its pos, 1...numqueries
  203.     for (i = 1; i <= NumQuery; i++) {
  204.         Query[Query[k[i]]] = i
  205.         Query[i] = Query[k[i]]    # for H option
  206.     }
  207.     }
  208.     Attempt = Query["a"]
  209.     Suc = Query["l"]
  210.     Logout = Query["o"]
  211.     POPmail = Query["p"]
  212.  
  213.     Abbreviate = \
  214.     NumQuery == 4 && (!("COLUMNS" in ENVIRON) || ENVIRON["COLUMNS"] < 97)
  215.     if ("t" in Options)
  216.     NewFormat = OldFormat = Options["t"]
  217.     else if (Abbreviate) {
  218.     NewFormat = "%b %d %H:%M"
  219.     OldFormat = "%b %d %Y"
  220.     }
  221.     if (Debug)
  222.     printf "Attempt=%s Suc=%s Logout=%s POPmail=%s\n",
  223.     Attempt,Suc,Logout,POPmail > "/dev/stderr"
  224.     if (Consistency) {
  225.     Logout = 1
  226.     Suc = 2
  227.     NumQuery = 2
  228.     Query[1] = "o"
  229.     Query[2] = "l"
  230.     }
  231.     else if (!(Attempt || Logout || Suc || POPmail)) {
  232.     Suc = 1
  233.     NumQuery = 1
  234.     Query[1] = "l"
  235.     }
  236.  
  237.     # Get user names to search for
  238.     if (!(ReadInput || AllUsers || LoggedInUsers) && ARGC < 2) {
  239.     ARGC = 2
  240.     ARGV[1] = id()
  241.     }
  242.     if (ARGC > 1)
  243.     NumUsers = FindLastLogins(ARGC,ARGV,Attempt,Suc,Logout,POPmail,
  244.     UserTimes,Users,0,Field,autoGCOS,CaseSensitive,autoCase,
  245.     regex,Full)
  246.     else if (ReadInput) {
  247.     while ((ret = (getline < "/dev/stdin")) == 1) {
  248.         for (i = 1; i <= NF; i++)
  249.         ARGV[i] = $i
  250.         NumUsers = \
  251.         FindLastLogins(i,ARGV,Attempt,Suc,Logout,POPmail,UserTimes,Users,0,
  252.         Field,autoGCOS,CaseSensitive,autoCase,regex,Full)
  253.     }
  254.     close("/dev/stdin")
  255.     if (ret)
  256.         print "Error reading stdin" > "/dev/stderr"
  257.     }
  258.     else if (AllUsers || LoggedInUsers) {
  259.     if (AllUsers)
  260.         while (getpwent(PWEnt,-1) == 1)
  261.         RealUsers[++RUserCt] = PWEnt[PW_NAME]
  262.     else {
  263.         cmd = "who -q -n1"
  264.         while ((cmd | getline) == 1)
  265.         if ($1 != "#" && !($1 in gotUsers)) {
  266.             gotUsers[$1]
  267.             RealUsers[++RUserCt] = $1
  268.         }
  269.         close(cmd)
  270.     }
  271.     NumUsers = \
  272.     FindLastLogins(RUserCt,RealUsers,Attempt,Suc,Logout,POPmail,UserTimes,
  273.     Users,1,Field,autoGCOS,CaseSensitive,autoCase,regex,Full)
  274.     }
  275.     PrintLines(UserTimes,Sort,Users,NumQuery,NumUsers,Header,Query,
  276.     QueryOpts,"n" in Options,Consistency,Abbreviate ? 14 : 16,NeverOnly,Suc,
  277.     PrintDate,PrintPeriod,mostRecent,printNever)
  278. }
  279.  
  280. # PWSearch: find entries in password file that match given criteria.
  281. # pat: Fixed string or pattern to compare to a password field.
  282. # Field: which password field to compare pat to, from the PW_* set.
  283. # Entries[]: The indexes of matching entries in the password database are
  284. #     returned as values in Entries[].  The index for each value in an
  285. #     integer giving the order in which it was found (starting with 1).
  286. # autoGCOS: If Field is PW_NAME and autoGCOS is true, pat is first searched
  287. #     for in the name field; if it is not found there, it is searched for in
  288. #     the "real name" field (part of the GCOS field).
  289. # ignoreCase: make matches be independent of case.
  290. # autoCase: If ignoreCase is false and autoCase is true, then matches are case
  291. #     case independent only if there are no upper case letters in pat.
  292. # regex: pat is treated as a regular expression and compared to the "real name"
  293. #     field.
  294. # Full: If regex is true, it is required to match the entire "real name" field.
  295. # Globals: Debug, pwFieldNames, PW_*
  296. # Return value: number of matching entries found (the highest index in
  297. #     Entries[]).
  298. function PWSearch(pat,Field,Entries,autoGCOS,ignoreCase,autoCase,regex,Full,
  299. oIC,PWEnt,num) {
  300.     if (ignoreCase && autoCase)
  301.     ignoreCase = (pat !~ /[A-Z]/)
  302.     num = 0
  303.     if (Field == PW_NAME && autoGCOS)
  304.     if (num = PWSearch(pat,Field,Entries,0,ignoreCase,0,regex,Full))
  305.         return num
  306.     else {
  307.         Field = PW_GCOS
  308.         if (Debug)
  309.         print "No match on name field, trying GCOS..." > "/dev/stderr"
  310.     }
  311.     if (Debug)
  312.     printf \
  313.     "Searching for %s in field %d (\"%s\") with case sensitivity %s,\n"\
  314.     "exact match %s\n",pat,Field,pwFieldNames[Field],
  315.     ignoreCase ? "off" : "on",Full ? "on" : "off"
  316.     if (regex) {
  317.     oIC = IGNORECASE
  318.     IGNORECASE = ignoreCase
  319.     if (Full)
  320.         pat = "^(" pat ")$"
  321.     setpwent()
  322.     if (Debug)
  323.         print "Pattern: " pat > "/dev/stderr"
  324.     while (getpwent(PWEnt,0,PW_REAL) != ":")
  325.         if (PWEnt[Field] ~ pat)
  326.         Entries[++num] = PWEnt[PW_RECORD]
  327.     IGNORECASE = oIC
  328.     }
  329.     else if (getpw(Field,pat,PWEnt,"",0,ignoreCase,Full)) {
  330.     Entries[++num] = PWEnt[PW_RECORD]
  331.     while (getpw(Field,pat,PWEnt,"",1,ignoreCase,Full))
  332.         Entries[++num] = PWEnt[PW_RECORD]
  333.     }
  334.     if (Debug)
  335.     printf "Got %d matches.\n",num > "/dev/stderr"
  336.     return num
  337. }
  338.  
  339. # PrintLines: print login/logout time report.
  340. # UserTimes[] contains the user login/logout times, indexed by User,QueryNum
  341. #     where User is the user name and QueryNum is an integer from 1 through n,
  342. #     where n is the number of times to be reported for each user.  Times will
  343. #     be listed in order of index.
  344. # Sort is true if the output should be sorted by the first time to be reported
  345. #     for each user.
  346. # If Consistency is true, only users who have file 1 and not file 2 are
  347. # reported on.
  348. # Users[] contains the names of users to be reported on, indexed starting from
  349. #     1.  Users will be reported on in order of their indexes.
  350. # NumQuery is the number of times to be reported for each user.
  351. # NumUsers is the highest index that may be used in Users[].
  352. # Header is true if a header should be printed.
  353. # Query[] contains the numbers of the queries being done, indexed 1..n
  354. # QueryNames[] contains the names of the queries, index by query number.
  355. function PrintLines(UserTimes,Sort,Users,NumQuery,NumUsers,Header,Query,
  356. QueryNames,RealName,Consistency,UserWidth,NeverOnly,Suc,PrintDate,PrintPeriod,
  357. mostRecent,printNever,
  358. i,User,Num,k,Key,UserFormat,TimeLength,TimeFormat,Name,CurTime,Time) {
  359.     # If successful-login was included in reports requested, and no data of any
  360.     # type was found for user, complain & do not include in main report.
  361.     if (!Consistency && Suc)
  362.     for (i = 1; i <= NumUsers; i++) {
  363.         if (!(i in Users))
  364.         continue
  365.         User = Users[i]
  366.         for (Num = 1; Num <= NumQuery; Num++)
  367.         if ((User,Num) in UserTimes)
  368.             break
  369.         if (Num > NumQuery) {        # No reports for this user
  370.         if (printNever)
  371.             NeverLoggedIn(User,NeverOnly,"/dev/stdout")
  372.         delete Users[i]
  373.         }
  374.     }
  375.     if (NeverOnly)
  376.     return
  377.     if (mostRecent) {
  378.     if (NumQuery > 1)    # Put most recent time in slot 1
  379.         for (i = 1; i <= NumUsers; i++) {
  380.         if (!(i in Users))
  381.             continue
  382.         User = Users[i]
  383.         Time = UserTimes[User,1]
  384.         for (Num = 2; Num <= NumQuery; Num++)
  385.             if (UserTimes[User,Num] > Time)
  386.             Time = UserTimes[User,Num]
  387.         UserTimes[User,1] = Time
  388.         }
  389.     NumQuery = 1
  390.     Query[1] = "r"
  391.     }
  392.     if (Sort) {
  393.     for (i in Users) {
  394.         User = Users[i]
  395.         if ((User,1) in UserTimes)
  396.         Key[User] = UserTimes[User,1]
  397.         else
  398.         Key[User] = 0
  399.     }
  400.     NumUsers = qsortArbIndByValue(Key,k)
  401.     for (i in k)
  402.         Users[i] = k[i]
  403.     }
  404.     if (Debug)
  405.     printf "%d report(s); %d user(s) found in Users[]\n",
  406.     NumQuery,NumUsers > "/dev/stderr"
  407.     UserFormat = "%-8s"
  408.     if (RealName)
  409.     UserFormat = UserFormat " %-" UserWidth "." UserWidth "s"
  410.     CurTime = systime()    # Do this once for consistency
  411.     # Make TimeLength be the maximum of the data names and the formatted data
  412.     # lengths.
  413.     for (Num = 1; Num <= NumQuery; Num++)
  414.     TimeLength = max(TimeLength,length(QueryNames[Query[Num]]))
  415.     # Get length of formatted data for epoch time 0 (maximum age)
  416.     TimeLength = max(TimeLength,length(FmtTime(0,0,PrintDate)))
  417.     if (PrintDate && PrintPeriod)
  418.     TimeLength = max(TimeLength,length(FmtTime(0,0,0)))
  419.     
  420.     TimeFormat = "  %-" TimeLength "s"
  421.     if (Header) {
  422.     # Before printing header, make sure there will be at least one report
  423.     for (i = 1; i <= NumUsers && !(i in Users); i++)
  424.         ;
  425.     if (i > NumUsers)
  426.         return
  427.     printf UserFormat,"User","Name"
  428.     for (Num = 1; Num <= NumQuery; Num++)
  429.         printf TimeFormat,QueryNames[Query[Num]]
  430.     print ""
  431.     }
  432.     for (i = 1; i <= NumUsers; i++) {
  433.     if (!(i in Users))
  434.         continue
  435.     User = Users[i]
  436.     # If doing a consistency check, logout time must be found and login
  437.     # time must not be found.
  438.     if (Consistency && (!((User,1) in UserTimes) || (User,2) in UserTimes))
  439.         continue
  440.     Name = getpwnam(User,PWEnt,PW_REAL)
  441.     if (length(Name) > UserWidth)
  442.         Name = substr(Name,1,UserWidth-1) ">"
  443.     printf UserFormat,User,Name
  444.     # If no data available for this user for a data type,
  445.     # or if the logout time has been requested and the logout time for the
  446.     # user is earlier than the login time, print a '-'
  447.     # Oops... that doesn't make sense, since a csh user's logout time will
  448.     # be earlier than login time when the user is logged in.
  449.     #    && (Num != Logout || !((User,Suc) in UserTimes) || \
  450.     #    UserTimes[User,Logout] >= UserTimes[User,Suc]) )
  451.  
  452.     # At least one of PrintDate and Print Period will be true.
  453.     for (Num = 1; Num <= NumQuery; Num++)
  454.         printf TimeFormat,((User,Num) in UserTimes) \
  455.         ? FmtTime(UserTimes[User,Num],CurTime,PrintDate) : "-"
  456.     print ""
  457.     if (PrintDate && PrintPeriod) {
  458.         printf UserFormat,"",""
  459.         for (Num = 1; Num <= NumQuery; Num++)
  460.         printf TimeFormat,((User,Num) in UserTimes) \
  461.         ? FmtTime(UserTimes[User,Num],CurTime,0) : "-"
  462.         print ""
  463.     }
  464.     }
  465. }
  466.  
  467. function lookupNames(argc,argv,IgnoreCase,ExactMatch,  PWEnt,i,outInd) {
  468.     CopyArr(argv,givenNames)
  469.     split("",argv)
  470.     outInd = 1
  471.     ReadPasswd()
  472.     for (i = 1; i < argc; i++) {
  473.     name = givenNames[i]
  474.     if ((user = getpwreal(name,PWEnt,PW_NAME,0,IgnoreCase,ExactMatch)) \
  475.     == ":")
  476.         printf "%s: %s: No user with this real name.\n",
  477.         Name,name > "/dev/stderr"
  478.     else {
  479.         argv[outInd++] = user
  480.         while ((user = \
  481.         getpwreal("",PWEnt,PW_NAME,1,IgnoreCase,ExactMatch)) != ":")
  482.         argv[outInd++] = user
  483.     }
  484.     }
  485.     return outInd
  486. }
  487.  
  488. # Input vars:
  489. # ARGC & ARGV[]: names of users to check.
  490. # Unsuc: Report last login attempt
  491. # Suc: Report last successful login
  492. # POPmail: Report last popmail access
  493. # Logout: Report last logout time
  494. # If nonzero, Unsuc, Suc, and Logout also give the index to store the
  495. # associated time under in UserTimes[].
  496. # If Consistency is true, no complaints are issued about users who have
  497. # no .lastlogin file, and they are included in Users[].
  498. # If Consistency is not true, users who have no .lastlogin file are not
  499. # included in Users[].
  500. # Output vars:
  501. # UserTimes[]: Login/logout times for users, indexed by UserName,TimeType
  502. #     where TimeType is in integer from 1-3 which indicates what order that
  503. #     time should be printed in relative to the others.
  504. # Users[]: Which users from ARGV[] actually existed, indexed by the order
  505. # they appeared in ARGV.
  506. # Globals: ONum ROOT
  507. function FindLastLogins(ARGC,ARGV,Unsuc,Suc,Logout,POPmail,UserTimes,Users,
  508. Consistency,Field,autoGCOS,CaseSensitive,autoCase,regex,Full,
  509. i,User,PWEnt,Home,Files,Cmd,Access,Modify,Create,Line,UserFiles,Shell2Hist,
  510. Shell,Elem,UsersInd,luser,Num,Entries,u,isPOP) {
  511.     # Note: the history file can be changed in any of these shells.
  512.     # zsh maintains history but has no default history file; try .zhistory
  513.     InitArr(Shell2Hist,"csh,tcsh,ksh,bash,zsh",
  514.     ".history,.history,.sh_history,.bash_history,.zhistory",",")
  515.     if (gcos)
  516.     ARGC = lookupNames(ARGC,ARGV,IgnoreCase,ExactMatch)
  517.     for (i = 1; i < ARGC; i++) {
  518.     User = ARGV[i]
  519.     Num = \
  520.     PWSearch(User,Field,Entries,autoGCOS,!CaseSensitive,autoCase,regex,Full)
  521.     if (Num)
  522.         for (u = 1; u <= Num; u++) {
  523.         PWGetFields(PWLines[Entries[u]],PWEnt,PW_REAL)
  524.         Users[++ONum] = User = PWEnt[PW_NAME]
  525.         UsersInd[User] = ONum
  526.         Home = ROOT PWEnt[PW_HOME]
  527.         Home2User[Home] = User
  528.         if (Unsuc || Suc)
  529.             Files = Files " " Home "/.lastlogin"
  530.         if (Logout) {
  531.             Shell = PWEnt[PW_SHELL]
  532.             sub(".*/","",Shell)
  533.             if (Shell in Shell2Hist)
  534.             Files = Files " " Home "/" Shell2Hist[Shell]
  535.         }
  536.         if (POPmail)
  537.             Files = Files " /usr/spool/mail/." User ".pop"
  538.         }
  539.     else
  540.         printf "%s: %s: No such user.\n",Name,User > "/dev/stderr"
  541.     }
  542.     if (Files == "")
  543.     return ONum
  544.     Cmd = "exec stat '-c ' -nfnamc " Files " 2>&1"
  545.     if (Debug)
  546.     print "stat command: " Cmd > "/dev/stderr"
  547.     # Lines from stat will be in this form:
  548.     #    name     access    modify    create
  549.     # .lastlogin 790501360 790501362 790501362
  550.     while ((Cmd | getline Line) == 1) {
  551.     if (Debug)
  552.         print "stat returned: " Line > "/dev/stderr"
  553.     sub("^stat: ","",Line)    # Error message from stat
  554.     
  555.     if (!split(Line,Elem," ")) {
  556.         if (Debug)
  557.         printf "Skipping empty stat line\n" > "/dev/stderr"
  558.         continue
  559.     }
  560.     Home = StatFile = Elem[1]
  561.     Access = Elem[2]
  562.     Modify = Elem[3]
  563.     Create = Elem[4]
  564.     sub("^.*/","",StatFile)    # Get rid of directory components
  565.     # In error messages, stat puts : after filename; get rid of it.
  566.     sub(":$","",StatFile)
  567.     # Get rid of trailing component of filename, leaving home directory
  568.     sub("/[^/]*$","",Home)
  569.     if (isPOP = \
  570.     (POPmail && Home == "/usr/spool/mail" && StatFile ~ "^\\..*\\.pop$")) {
  571.         User = substr(StatFile,2,length(StatFile)-5)
  572.         if (Debug)
  573.         printf "Got popmail file for: <%s>\n",User > "/dev/stderr"
  574.     }
  575.     else {
  576.         if (!(Home in Home2User)) {
  577.         printf "Oops... got unknown home '%s' from stat output:\n%s\n",
  578.         Home,Line > "/dev/stderr"
  579.         continue
  580.         }
  581.         User = Home2User[Home]
  582.     }
  583.     # If user isn't in Users[], we already determined that this user
  584.     # has never logged in, so skip any other data for it.
  585.     if (!(User in UsersInd)) {
  586.         if (Debug)
  587.         printf "Ignoring further data for %s\n",User > "/dev/stderr"
  588.         continue
  589.     }
  590.     if (Access !~ "^[0-9]+$") {
  591.         if (Line !~ "No such file") {
  592.         sub("^[^ ]* ","")
  593.         printf "Cannot stat %s's %s file: %s\n",User,StatFile,Line \
  594.         > "/dev/stderr"
  595.         }
  596.         continue
  597.     }
  598.     if (Debug)
  599.         printf \
  600.     "user: %s  suc: %d  unsuc: %d  statfile: %s  modify: %d  create: %d\n",
  601.     User,Suc,Unsuc,StatFile,Modify,Create > "/dev/stderr"
  602.     if (StatFile == ".lastlogin") {
  603.         if (Suc)
  604.         UserTimes[User,Suc] = Modify
  605.         if (Unsuc)
  606.         UserTimes[User,Unsuc] = Create
  607.     }
  608.     else if (isPOP)
  609.         UserTimes[User,POPmail] = Modify
  610.     else if (Logout)
  611.         UserTimes[User,Logout] = Modify
  612.     }
  613.     close(Cmd)
  614.     return ONum
  615. }
  616.  
  617. function NeverLoggedIn(User,NeverOnly,file,  Home) {
  618.     Home = getpwnam(User,PWEnt,PW_HOME)
  619.     CmdReadLine("stat -nt%y/%m/%d -fC " Home "/.*profile " \
  620.     Home "/.login 2>/dev/null",0)
  621.     if (neverOnly)
  622.     printf "%8s %8s %s\n",$1,User,getpwnam(User,PWEnt,PW_REAL)
  623.     else {
  624.     printf "%s (%s): Never logged in",
  625.     User,getpwnam(User,PWEnt,PW_REAL) > file
  626.     if ($1 ~ "^[0-9][0-9]/[0-1][0-9]/[0-3][0-9]$")
  627.         print " (account created on " $1 ")." > file
  628.     else
  629.         print "." > file
  630.     }
  631. }
  632.  
  633. function FmtTime(Time,CurTime,PrintDate,  Age,ret) {
  634.     if (PrintDate) {
  635.     # If older than a half year ago
  636.     if (Time < (CurTime - 182 * 24 * 3600))
  637.         return OldFormat ? strftime(OldFormat,Time) : Time
  638.     else
  639.         return NewFormat ? strftime(NewFormat,Time) : Time
  640.     }
  641.     else {
  642.     Age = CurTime - Time
  643.     if (Age < 0) {    # should not happen
  644.         ret = "-"
  645.         Age = -Age
  646.     }
  647.     if (Age >= 86400) {
  648.         ret = ret int(Age/86400) "d "
  649.         Age = Age % 86400
  650.     }
  651.     Age = int(Age / 60)
  652.     ret = sprintf("%s%d:%02d",ret,Age/60,Age%60)
  653.     return ret
  654.     }
  655. }
  656.  
  657. # MakeSet: make a set from a list.
  658. # An index with the name of each element of the list
  659. # is created in the given array.
  660. # Input variables: 
  661. # Elements is a string containing the list of elements.
  662. # Sep is the character that separates the elements of the list.
  663. # Output variables:
  664. # Set is the array.
  665. # Return value: the number of elements added to the set.
  666. function MakeSet(Set,Elements,Sep,  i,Num,Names) {
  667.     Num = split(Elements,Names,Sep)
  668.     for (i = 1; i <= Num; i++)
  669.     Set[Names[i]]
  670.     return Num
  671. }
  672.  
  673. # Returns the number of elements in set Set
  674. function NumElem(Set,  elem,Num) {
  675.     for (elem in Set)
  676.     Num++
  677.     return Num
  678. }
  679.  
  680. # id returns the login name of the user who owns the current process
  681. function id(  Cmd,line,elem) {
  682.     Cmd = "exec /usr/bin/id"
  683.     Cmd | getline line
  684.     split(line,elem,"[()]")
  685.     close(Cmd)
  686.     return elem[2]
  687. }
  688.  
  689. # Arr is an array of values with arbitrary indices.
  690. # Array k is returned with numeric indices 1..n.
  691. # The values in k are the indices of array arr, 
  692. # ordered so that if array arr is stepped through
  693. # in the order arr[k[1]] .. arr[k[n]], it will be stepped
  694. # through in order of the values of its elements.
  695. # The return value is the number of elements in the array (n).
  696. function qsort_arb_ind(arr,k,  ArrInd,ElNum) {
  697.     ElNum = 0
  698.     for (ArrInd in arr)
  699.     k[++ElNum] = ArrInd
  700.     qsortseg(arr,k,1,ElNum)
  701.     return ElNum
  702. }
  703.  
  704. # @(#) qsortseg.awk 2.0 94/01/20
  705. # Non-recursive qsort.  Slightly slower than recursive qsort, 
  706. # but gawk 2.15.3 chokes on recursive version with internal error.
  707. # Sort a segment of an array.
  708. # Arr[] contains data with arbitrary indices.
  709. # k[] has indices 1..nelem, with the indices of Arr[] as values.
  710. # This function sorts the elements of Arr that are pointed to by
  711. # k[start..end], swapping the values of elements of k[] so that
  712. # when this function returns Arr[k[start..end]] will be in order.
  713. function qsortseg(arr,k,start,end,
  714. left,right,sepval,tmp,tmpe,tmps,ind,val,S,E,stackptr) {
  715.     stackptr = 0
  716.     S[0] = start
  717.     E[0] = end
  718.     while (stackptr >= 0) {
  719.     # pop values from stack
  720.     left = start = S[stackptr]
  721.     right = end = E[stackptr]
  722.     stackptr--
  723.     # handle two-element case explicitly for a tiny speedup
  724.     if ((end - start) == 1) {
  725.         if (arr[tmps = k[start]] > arr[tmpe = k[end]]) {
  726.         k[start] = tmpe
  727.         k[end] = tmps
  728.         }
  729.         continue
  730.     }
  731.     sepval = arr[k[int((left + right) / 2)]]
  732.     # Make every element <= sepval be to the left of every element > sepval
  733.     while (left < right) {
  734.         while (arr[k[left]] < sepval)
  735.         left++
  736.         while (arr[k[right]] > sepval)
  737.         right--
  738.         if (left < right) {
  739.         tmp = k[left]
  740.         k[left++] = k[right]
  741.         k[right--] = tmp 
  742.         }
  743.     }
  744.     if (left == right)
  745.         if (arr[k[left]] < sepval)
  746.         left++
  747.         else
  748.         right--
  749.     if (start < right) {
  750.         S[++stackptr] = start
  751.         E[stackptr] = right
  752.     }
  753.     if (left < end) {
  754.         S[++stackptr] = left
  755.         E[stackptr] = end
  756.     }
  757.     }
  758. }
  759.  
  760. # Arr is an array of values with arbitrary indices.
  761. # Array k is returned with numeric indices 1..n.
  762. # The values in k are the indices of array arr, 
  763. # ordered so that if array arr is stepped through
  764. # in the order arr[k[1]] .. arr[k[n]], it will be stepped
  765. # through in order of the values of its indices.
  766. # The return value is the number of elements in the array (n).
  767. function qsort_by_index(arr,k,  ArrInd,end) {
  768.     end = 0
  769.     for (ArrInd in arr)
  770.     k[++end] = ArrInd
  771.     qsort_num_ind(k,1,end)
  772.     return end
  773. }
  774.  
  775. # Indexes do not preserve numeric type, so must be forced
  776. function qsort_by_num_index(arr,k,  ArrInd,end) {
  777.     end = 0
  778.     for (ArrInd in arr)
  779.     k[++end] = ArrInd+0
  780.     qsort_num_ind(k,1,end)
  781.     return end
  782. }
  783.  
  784. # qsort_num_ind: sort an array
  785. # arr is the array to be sorted.
  786. # It must have contiguous numeric indexes.
  787. # start and end are the starting and ending indexes of the range to be sorted.
  788. function qsort_num_ind(arr,start,end,  left,right,sepval,tmp,tmpe,tmps) {
  789.     # handle two-element case explicitly for a tiny speedup
  790.     if ((start - end) == 1) {
  791.     if ((tmps = arr[start]) > (tmpe = arr[end])) {
  792.         arr[start] = tmpe
  793.         arr[end] = tmps
  794.     }
  795.     return
  796.     }
  797.     left = start+0
  798.     right = end+0
  799.     sepval = arr[int((left + right) / 2)]
  800.     while (left < right) {
  801.     while (arr[left] < sepval)
  802.         left++
  803.     while (arr[right] > sepval)
  804.         right--
  805.     if (left <= right) {
  806.         tmp = arr[left]
  807.         arr[left++] = arr[right]
  808.         arr[right--] = tmp 
  809.     }
  810.     }
  811.     if (start < right)
  812.     qsort_num_ind(arr,start,right)
  813.     if (left < end)
  814.     qsort_num_ind(arr,left,end)
  815. }
  816.  
  817. function abort(s) {
  818.     print s
  819.     exit(1)
  820. }
  821.  
  822. ### Begin Strings routines
  823.  
  824. # Delete the string starting at Start and having length Num from the middle
  825. # of string S, and return the remaining part.
  826. function DelStr(S,Start,Num) {
  827.     return substr(S,1,Start - 1) substr(S,Start+Num)
  828. }
  829.  
  830. # Insert NewStr into S at position Pos (between the Pos-1 and the Pos
  831. # characters).  S is padded with spaces if neccessary.
  832. function InsertStr(S,Pos,NewStr,  e) {
  833.     e = length(S)+1    # The position after the end of S
  834.     if (e >= Pos)
  835.     return substr(S,1,Pos-1) NewStr substr(S,Pos)
  836.     for (; e < Pos; e++)
  837.     S = S " "
  838.     return S NewStr
  839. }
  840.  
  841. # Search for char C in string S starting at position Pos, in the direction
  842. # specified by Dir (1 = forward, -1 = backward).  
  843. # Return position char found at for success, 0 if not found before start or end
  844. # of string.
  845. function FindC(S,Pos,C,Dir,  FoundC) {
  846.     while (Pos > 0 && (FoundC = substr(S,Pos,1)) != C && FoundC != "")
  847.     Pos += Dir
  848.     if (FoundC == C)
  849.     return Pos
  850.     else
  851.     return 0
  852. }
  853.  
  854. # Split string S into array Arr, one character per index, starting with 1.
  855. # The number of characters in the string is returned.
  856. function SplitS(S,Arr,  len,i) {
  857.     len = length(S)
  858.     for (i = 1; i <= len; i++)
  859.     Arr[i] = substr(S,i,1)
  860.     return len
  861. }
  862.  
  863. # Paste NewStr onto S at position Pos, overwriting what was there
  864. # S is padded with spaces if neccessary.
  865. function PasteStr(S,Pos,NewStr,  e) {
  866.     e = length(S)+1    # The position after the end of S
  867.     if (e >= Pos)
  868.     return substr(S,1,Pos-1) NewStr substr(S,Pos+length(NewStr))
  869.     for (; e < Pos; e++)
  870.     S = S " "
  871.     return S NewStr
  872. }
  873.  
  874. ### End Strings routines
  875.  
  876. # Put a list of login shells (from /etc/shells) into set LoginShells[].
  877. # Returns -1 if /etc/shells could not be read, else the number of shells found.
  878. function ReadShells(LoginShells,  ret,Num,Line) {
  879.     while (ret = ((getline Line < "/etc/shells") == 1))
  880.     if (Line ~ "^/") {
  881.         Num++
  882.         sub(/[ \t]+/,"",Line)
  883.         LoginShells[Line]
  884.     }
  885.     close("/etc/shells")
  886.     _DidReadShells = 1
  887.     return ret ? -1 : Num
  888. }
  889.  
  890. ### Begin array routines
  891.  
  892. # InitArr: Initialize an array with values.
  893. # Ind and Vals are separated into lists on Sep.
  894. # For each item in Ind, an index with that name is created in Arr[],
  895. # and the value with the same position in Vals is stored in it.
  896. # Global variables: none.
  897. function InitArr(Arr,Ind,Vals,sep,  numind,indnames,values) {
  898.     split(Ind,indnames,sep)
  899.     split(Vals,values,sep)
  900.     for (numind in indnames)
  901.     Arr[indnames[numind]] = values[numind]
  902. }
  903.  
  904. function ClearArr(Arr,  Elem) {
  905.     for (Elem in Arr)
  906.     delete Arr[Elem]
  907. }
  908.  
  909. function CopyArr(From,To,  Elem) {
  910.     for (Elem in From)
  911.     To[Elem] = From[Elem]
  912. }
  913.  
  914. # Subtract the values in Subtrahend from those in Minuend
  915. function SubtractArr(Minuend,Subtrahend,  Elem) {
  916.     for (Elem in Subtrahend)
  917.     Minuend[Elem] -= Subtrahend[Elem]
  918. }
  919. # For each element of the array In, an element is created in Out having
  920. # an index equal to the value of the element in In and a value equal to 
  921. # the index of the element in In.
  922. function Invert(In,Out,  Index) {
  923.     for (Index in In)
  924.     Out[In[Index]] = Index
  925. }
  926.  
  927. # Assign: make an array from a list of assignments.
  928. # An index with the name of each variable in the list is created in the array.
  929. # Its value is set to the value given for it.
  930. # Input variables: 
  931. # Elements is a string containing the list of variable-value pairs.
  932. # Sep is the string that separates the pairs in the list.
  933. # AssignOp is the string that separates variables from values.
  934. # Output variables:
  935. # Arr is the array.
  936. # Return value: the number of elements added to the set.
  937. # Example:
  938. # Assign(Arr,"foo=blot bar=blat baz=blit"," ","=")
  939. function Assign(Arr,Elements,Sep,AssignOp,
  940. Num,Names,Elem,Assignments,Assignment,i) {
  941.     Num = split(Elements,Assignments,Sep)
  942.     for (i = 1; i <= Num; i++) {
  943.     Assignment = Assignments[i]
  944.     Ind = index(Assignment,AssignOp)
  945.     Arr[substr(Assignment,1,Ind - 1)] = substr(Assignment,Ind + 1)
  946.     }
  947.     return Num
  948. }
  949.  
  950. # Packs Arr[], which should have integer indices starting at or above n, to
  951. # contiguous integer indices starting with n.
  952. # If n is not given it defaults to 0.
  953. # Num should be the number of elements in Arr.
  954. function PackArr(Arr,Num,n,  NewInd,OldInd) {
  955.     NewInd = OldInd = n+0
  956.     for (; Num; Num--) {
  957.     while (!(OldInd in Arr))
  958.         OldInd++
  959.     if (NewInd != OldInd) {
  960.         Arr[NewInd] = Arr[OldInd]
  961.         delete Arr[OldInd]
  962.     }
  963.     OldInd++
  964.     NewInd++
  965.     }
  966. }
  967. ### End array routines
  968. ### Start of ProcArgs library
  969. # @(#) ProcArgs 1.11 96/12/08
  970. # 92/02/29 john h. dubois iii (john@armory.com)
  971. # 93/07/18 Added "#" arg type
  972. # 93/09/26 Do not count -h against MinArgs
  973. # 94/01/01 Stop scanning at first non-option arg.  Added ">" option type.
  974. #          Removed meaning of "+" or "-" by itself.
  975. # 94/03/08 Added & option and *()< option types.
  976. # 94/04/02 Added NoRCopt to Opts()
  977. # 94/06/11 Mark numeric variables as such.
  978. # 94/07/08 Opts(): Do not require any args if h option is given.
  979. # 95/01/22 Record options given more than once.  Record option num in argv.
  980. # 95/06/08 Added ExclusiveOptions().
  981. # 96/01/20 Let rcfiles be a colon-separated list of filenames.
  982. #          Expand $VARNAME at the start of its filenames.
  983. #          Let varname=0 and -option- turn off an option.
  984. # 96/05/05 Changed meaning of 7th arg to Opts; now can specify exactly how many
  985. #          of the vars should be searched for in the environment.
  986. #          Check for duplicate rcfiles.
  987. # 96/05/13 Return more specific error values.  Note: ProcArgs() and InitOpts()
  988. #          now return various negatives values on error, not just -1, and
  989. #          Opts() may set Err to various positive values, not just 1.
  990. #          Added AllowUnrecOpt.
  991. # 96/05/23 Check type given for & option
  992. # 96/06/15 Re-port to awk
  993. # 96/10/01 Moved file-reading code into ReadConfFile(), so that it can be
  994. #          used by other functions.
  995. # 96/10/15 Added OptChars
  996. # 96/11/01 Added exOpts arg to Opts()
  997. # 96/11/16 Added ; type
  998. # 96/12/08 Added Opt2Set() & Opt2Sets()
  999. # 96/12/27 Added CmdLineOpt()
  1000.  
  1001. # optlist is a string which contains all of the possible command line options.
  1002. # A character followed by certain characters indicates that the option takes
  1003. # an argument, with type as follows:
  1004. # :    String argument
  1005. # ;    Non-empty string argument
  1006. # *    Floating point argument
  1007. # (    Non-negative floating point argument
  1008. # )    Positive floating point argument
  1009. # #    Integer argument
  1010. # <    Non-negative integer argument
  1011. # >    Positive integer argument
  1012. # The only difference the type of argument makes is in the runtime argument
  1013. # error checking that is done.
  1014.  
  1015. # The & option is a special case used to get numeric options without the
  1016. # user having to give an option character.  It is shorthand for [-+.0-9].
  1017. # If & is included in optlist and an option string that begins with one of
  1018. # these characters is seen, the value given to "&" will include the first
  1019. # char of the option.  & must be followed by a type character other than ":"
  1020. # or ";".
  1021. # Note that if e.g. &> is given, an option of -.5 will produce an error.
  1022.  
  1023. # Strings in argv[] which begin with "-" or "+" are taken to be
  1024. # strings of options, except that a string which consists solely of "-"
  1025. # or "+" is taken to be a non-option string; like other non-option strings,
  1026. # it stops the scanning of argv and is left in argv[].
  1027. # An argument of "--" or "++" also stops the scanning of argv[] but is removed.
  1028. # If an option takes an argument, the argument may either immediately
  1029. # follow it or be given separately.
  1030. # "-" and "+" options are treated the same.  "+" is allowed because most awks
  1031. # take any -options to be arguments to themselves.  gawk 2.15 was enhanced to
  1032. # stop scanning when it encounters an unrecognized option, though until 2.15.5
  1033. # this feature had a flaw that caused problems in some cases.  See the OptChars
  1034. # parameter to explicitly set the option-specifier characters.
  1035.  
  1036. # If an option that does not take an argument is given,
  1037. # an index with its name is created in Options and its value is set to the
  1038. # number of times it occurs in argv[].
  1039.  
  1040. # If an option that does take an argument is given, an index with its name is
  1041. # created in Options and its value is set to the value of the argument given
  1042. # for it, and Options[option-name,"count"] is (initially) set to the 1.
  1043. # If an option that takes an argument is given more than once,
  1044. # Options[option-name,"count"] is incremented, and the value is assigned to
  1045. # the index (option-name,instance) where instance is 2 for the second occurance
  1046. # of the option, etc.
  1047. # In other words, the first time an option with a value is encountered, the
  1048. # value is assigned to an index consisting only of its name; for any further
  1049. # occurances of the option, the value index has an extra (count) dimension.
  1050.  
  1051. # The sequence number for each option found in argv[] is stored in
  1052. # Options[option-name,"num",instance], where instance is 1 for the first
  1053. # occurance of the option, etc.  The sequence number starts at 1 and is
  1054. # incremented for each option, both those that have a value and those that
  1055. # do not.  Options set from a config file have a value of 0 assigned to this.
  1056.  
  1057. # Options and their arguments are deleted from argv.
  1058. # Note that this means that there may be gaps left in the indices of argv[].
  1059. # If compress is nonzero, argv[] is packed by moving its elements so that
  1060. # they have contiguous integer indices starting with 0.
  1061. # Option processing will stop with the first unrecognized option, just as
  1062. # though -- was given except that unlike -- the unrecognized option will not be
  1063. # removed from ARGV[].  Normally, an error value is returned in this case.
  1064. # If AllowUnrecOpt is true, it is not an error for an unrecognized option to
  1065. # be found, so the number of remaining arguments is returned instead.
  1066. # If OptChars is not a null string, it is the set of characters that indicate
  1067. # that an argument is an option string if the string begins with one of the
  1068. # characters.  A string consisting solely of two of the same option-indicator
  1069. # characters stops the scanning of argv[].  The default is "-+".
  1070. # argv[0] is not examined.
  1071. # The number of arguments left in argc is returned.
  1072. # If an error occurs, the global string OptErr is set to an error message
  1073. # and a negative value is returned.
  1074. # Current error values:
  1075. # -1: option that required an argument did not get it.
  1076. # -2: argument of incorrect type supplied for an option.
  1077. # -3: unrecognized (invalid) option.
  1078. function ProcArgs(argc,argv,OptList,Options,compress,AllowUnrecOpt,OptChars,
  1079. ArgNum,ArgsLeft,Arg,ArgLen,ArgInd,Option,Pos,NumOpt,Value,HadValue,specGiven,
  1080. NeedNextOpt,GotValue,OptionNum,Escape,dest,src,count,c,OptTerm,OptCharSet)
  1081. {
  1082. # ArgNum is the index of the argument being processed.
  1083. # ArgsLeft is the number of arguments left in argv.
  1084. # Arg is the argument being processed.
  1085. # ArgLen is the length of the argument being processed.
  1086. # ArgInd is the position of the character in Arg being processed.
  1087. # Option is the character in Arg being processed.
  1088. # Pos is the position in OptList of the option being processed.
  1089. # NumOpt is true if a numeric option may be given.
  1090.     ArgsLeft = argc
  1091.     NumOpt = index(OptList,"&")
  1092.     OptionNum = 0
  1093.     if (OptChars == "")
  1094.     OptChars = "-+"
  1095.     while (OptChars != "") {
  1096.     c = substr(OptChars,1,1)
  1097.     OptChars = substr(OptChars,2)
  1098.     OptCharSet[c]
  1099.     OptTerm[c c]
  1100.     }
  1101.     for (ArgNum = 1; ArgNum < argc; ArgNum++) {
  1102.     Arg = argv[ArgNum]
  1103.     if (length(Arg) < 2 || !((specGiven = substr(Arg,1,1)) in OptCharSet))
  1104.         break    # Not an option; quit
  1105.     if (Arg in OptTerm) {
  1106.         delete argv[ArgNum]
  1107.         ArgsLeft--
  1108.         break
  1109.     }
  1110.     ArgLen = length(Arg)
  1111.     for (ArgInd = 2; ArgInd <= ArgLen; ArgInd++) {
  1112.         Option = substr(Arg,ArgInd,1)
  1113.         if (NumOpt && Option ~ /[-+.0-9]/) {
  1114.         # If this option is a numeric option, make its flag be & and
  1115.         # its option string flag position be the position of & in
  1116.         # the option string.
  1117.         Option = "&"
  1118.         Pos = NumOpt
  1119.         # Prefix Arg with a char so that ArgInd will point to the
  1120.         # first char of the numeric option.
  1121.         Arg = "&" Arg
  1122.         ArgLen++
  1123.         }
  1124.         # Find position of flag in option string, to get its type (if any).
  1125.         # Disallow & as literal flag.
  1126.         else if (!(Pos = index(OptList,Option)) || Option == "&") {
  1127.         if (AllowUnrecOpt) {
  1128.             Escape = 1
  1129.             break
  1130.         }
  1131.         else {
  1132.             OptErr = "Invalid option: " specGiven Option
  1133.             return -3
  1134.         }
  1135.         }
  1136.  
  1137.         # Find what the value of the option will be if it takes one.
  1138.         # NeedNextOpt is true if the option specifier is the last char of
  1139.         # this arg, which means that if the option requires a value it is
  1140.         # the next arg.
  1141.         if (NeedNextOpt = (ArgInd >= ArgLen)) { # Value is the next arg
  1142.         if (GotValue = ArgNum + 1 < argc)
  1143.             Value = argv[ArgNum+1]
  1144.         }
  1145.         else {    # Value is included with option
  1146.         Value = substr(Arg,ArgInd + 1)
  1147.         GotValue = 1
  1148.         }
  1149.  
  1150.         if (HadValue = AssignVal(Option,Value,Options,
  1151.         substr(OptList,Pos + 1,1),GotValue,"",++OptionNum,!NeedNextOpt,
  1152.         specGiven)) {
  1153.         if (HadValue < 0)    # error occured
  1154.             return HadValue
  1155.         if (HadValue == 2)
  1156.             ArgInd++    # Account for the single-char value we used.
  1157.         else {
  1158.             if (NeedNextOpt) {    # option took next arg as value
  1159.             delete argv[++ArgNum]
  1160.             ArgsLeft--
  1161.             }
  1162.             break    # This option has been used up
  1163.         }
  1164.         }
  1165.     }
  1166.     if (Escape)
  1167.         break
  1168.     # Do not delete arg until after processing of it, so that if it is not
  1169.     # recognized it can be left in ARGV[].
  1170.     delete argv[ArgNum]
  1171.     ArgsLeft--
  1172.     }
  1173.     if (compress != 0) {
  1174.     dest = 1
  1175.     src = argc - ArgsLeft + 1
  1176.     for (count = ArgsLeft - 1; count; count--) {
  1177.         ARGV[dest] = ARGV[src]
  1178.         dest++
  1179.         src++
  1180.     }
  1181.     }
  1182.     return ArgsLeft
  1183. }
  1184.  
  1185. # Assignment to values in Options[] occurs only in this function.
  1186. # Option: Option specifier character.
  1187. # Value: Value to be assigned to option, if it takes a value.
  1188. # Options[]: Options array to return values in.
  1189. # ArgType: Argument type specifier character.
  1190. # GotValue: Whether any value is available to be assigned to this option.
  1191. # Name: Name of option being processed.
  1192. # OptionNum: Number of this option (starting with 1) if set in argv[],
  1193. #     or 0 if it was given in a config file or in the environment.
  1194. # SingleOpt: true if the value (if any) that is available for this option was
  1195. #     given as part of the same command line arg as the option.  Used only for
  1196. #     options from the command line.
  1197. # specGiven is the option specifier character use, if any (e.g. - or +),
  1198. # for use in error messages.
  1199. # Global variables: OptErr
  1200. # Return value: negative value on error, 0 if option did not require an
  1201. # argument, 1 if it did & used the whole arg, 2 if it required just one char of
  1202. # the arg.
  1203. # Current error values:
  1204. # -1: Option that required an argument did not get it.
  1205. # -2: Value of incorrect type supplied for option.
  1206. # -3: Bad type given for option &
  1207. function AssignVal(Option,Value,Options,ArgType,GotValue,Name,OptionNum,
  1208. SingleOpt,specGiven,  UsedValue,Err,NumTypes) {
  1209.     # If option takes a value...    [
  1210.     NumTypes = "*()#<>]"
  1211.     if (Option == "&" && ArgType !~ "[" NumTypes) {    # ]
  1212.     OptErr = "Bad type given for & option"
  1213.     return -3
  1214.     }
  1215.  
  1216.     if (UsedValue = (ArgType ~ "[:;" NumTypes)) {    # ]
  1217.     if (!GotValue) {
  1218.         if (Name != "")
  1219.         OptErr = "Variable requires a value -- " Name
  1220.         else
  1221.         OptErr = "option requires an argument -- " Option
  1222.         return -1
  1223.     }
  1224.     if ((Err = CheckType(ArgType,Value,Option,Name,specGiven)) != "") {
  1225.         OptErr = Err
  1226.         return -2
  1227.     }
  1228.     # Mark this as a numeric variable; will be propogated to Options[] val.
  1229.     if (ArgType != ":" && ArgType != ";")
  1230.         Value += 0
  1231.     if ((Instance = ++Options[Option,"count"]) > 1)
  1232.         Options[Option,Instance] = Value
  1233.     else
  1234.         Options[Option] = Value
  1235.     }
  1236.     # If this is an environ or rcfile assignment & it was given a value...
  1237.     else if (!OptionNum && Value != "") {
  1238.     UsedValue = 1
  1239.     # If the value is "0" or "-" and this is the first instance of it,
  1240.     # do not set Options[Option]; this allows an assignment in an rcfile to
  1241.     # turn off an option (for the simple "Option in Options" test) in such
  1242.     # a way that it cannot be turned on in a later file.
  1243.     if (!(Option in Options) && (Value == "0" || Value == "-"))
  1244.         Instance = 1
  1245.     else
  1246.         Instance = ++Options[Option]
  1247.     # Save the value even though this is a flag
  1248.     Options[Option,Instance] = Value
  1249.     }
  1250.     # If this is a command line flag and has a - following it in the same arg,
  1251.     # it is being turned off.
  1252.     else if (OptionNum && SingleOpt && substr(Value,1,1) == "-") {
  1253.     UsedValue = 2
  1254.     if (Option in Options)
  1255.         Instance = ++Options[Option]
  1256.     else
  1257.         Instance = 1
  1258.     Options[Option,Instance]
  1259.     }
  1260.     # If this is a flag assignment without a value, increment the count for the
  1261.     # flag unless it was turned off.  The indicator for a flag being turned off
  1262.     # is that the flag index has not been set in Options[] but it has an
  1263.     # instance count.
  1264.     else if (Option in Options || !((Option,1) in Options))
  1265.     # Increment number of times this flag seen; will inc null value to 1
  1266.     Instance = ++Options[Option]
  1267.     Options[Option,"num",Instance] = OptionNum
  1268.     return UsedValue
  1269. }
  1270.  
  1271. # Option is the option letter
  1272. # Value is the value being assigned
  1273. # Name is the var name of the option, if any
  1274. # ArgType is one of:
  1275. # :    String argument
  1276. # ;    Non-null string argument
  1277. # *    Floating point argument
  1278. # (    Non-negative floating point argument
  1279. # )    Positive floating point argument
  1280. # #    Integer argument
  1281. # <    Non-negative integer argument
  1282. # >    Positive integer argument
  1283. # specGiven is the option specifier character use, if any (e.g. - or +),
  1284. # for use in error messages.
  1285. # Returns null on success, err string on error
  1286. function CheckType(ArgType,Value,Option,Name,specGiven,  Err,ErrStr) {
  1287.     if (ArgType == ":")
  1288.     return ""
  1289.     if (ArgType == ";") {
  1290.     if (Value == "")
  1291.         Err = "must be a non-empty string"
  1292.     }
  1293.     # A number begins with optional + or -, and is followed by a string of
  1294.     # digits or a decimal with digits before it, after it, or both
  1295.     else if (Value !~ /^[-+]?([0-9]+|[0-9]*\.[0-9]+|[0-9]+\.)$/)
  1296.     Err = "must be a number"
  1297.     else if (ArgType ~ "[#<>]" && Value ~ /\./)
  1298.     Err = "may not include a fraction"
  1299.     else if (ArgType ~ "[()<>]" && Value < 0)
  1300.     Err = "may not be negative"
  1301.     # (
  1302.     else if (ArgType ~ "[)>]" && Value == 0)
  1303.     Err = "must be a positive number"
  1304.     if (Err != "") {
  1305.     ErrStr = "Bad value \"" Value "\".  Value assigned to "
  1306.     if (Name != "")
  1307.         return ErrStr "variable " substr(Name,1,1) " " Err
  1308.     else {
  1309.         if (Option == "&")
  1310.         Option = Value
  1311.         return ErrStr "option " specGiven substr(Option,1,1) " " Err
  1312.     }
  1313.     }
  1314.     else
  1315.     return ""
  1316. }
  1317.  
  1318. # Note: only the above functions are needed by ProcArgs.
  1319. # The rest of these functions call ProcArgs() and also do other
  1320. # option-processing stuff.
  1321.  
  1322. # Opts: Process command line arguments.
  1323. # Opts processes command line arguments using ProcArgs()
  1324. # and checks for errors.  If an error occurs, a message is printed
  1325. # and the program is exited.
  1326. #
  1327. # Input variables:
  1328. # Name is the name of the program, for error messages.
  1329. # Usage is a usage message, for error messages.
  1330. # OptList the option description string, as used by ProcArgs().
  1331. # MinArgs is the minimum number of non-option arguments that this
  1332. # program should have, non including ARGV[0] and +h.
  1333. # If the program does not require any non-option arguments,
  1334. # MinArgs should be omitted or given as 0.
  1335. # rcFiles, if given, is a colon-seprated list of filenames to read for
  1336. # variable initialization.  If a filename begins with ~/, the ~ is replaced
  1337. # by the value of the environment variable HOME.  If a filename begins with
  1338. # $, the part from the character after the $ up until (but not including)
  1339. # the first character not in [a-zA-Z0-9_] will be searched for in the
  1340. # environment; if found its value will be substituted, if not the filename will
  1341. # be discarded.
  1342. # rcfiles are read in the order given.
  1343. # Values given in them will not override values given on the command line,
  1344. # and values given in later files will not override those set in earlier
  1345. # files, because AssignVal() will store each with a different instance index.
  1346. # The first instance of each variable, either on the command line or in an
  1347. # rcfile, will be stored with no instance index, and this is the value
  1348. # normally used by programs that call this function.
  1349. # VarNames is a comma-separated list of variable names to map to options,
  1350. # in the same order as the options are given in OptList.
  1351. # If EnvSearch is given and nonzero, the first EnvSearch variables will also be
  1352. # searched for in the environment.  If set to -1, all values will be searched
  1353. # for in the environment.  Values given in the environment will override
  1354. # those given in the rcfiles but not those given on the command line.
  1355. # NoRCopt, if given, is an additional letter option that if given on the
  1356. # command line prevents the rcfiles from being read.
  1357. # See ProcArgs() for a description of AllowUnRecOpt and optChars, and
  1358. # ExclusiveOptions() for a description of exOpts.
  1359. # Special options:
  1360. # If x is made an option and is given, some debugging info is output.
  1361. # h is assumed to be the help option.
  1362.  
  1363. # Global variables:
  1364. # The command line arguments are taken from ARGV[].
  1365. # The arguments that are option specifiers and values are removed from
  1366. # ARGV[], leaving only ARGV[0] and the non-option arguments.
  1367. # The number of elements in ARGV[] should be in ARGC.
  1368. # After processing, ARGC is set to the number of elements left in ARGV[].
  1369. # The option values are put in Options[].
  1370. # On error, Err is set to a positive integer value so it can be checked for in
  1371. # an END block.
  1372. # Return value: The number of elements left in ARGV is returned.
  1373. # Must keep OptErr global since it may be set by InitOpts().
  1374. function Opts(Name,Usage,OptList,MinArgs,rcFiles,VarNames,EnvSearch,NoRCopt,
  1375. AllowUnrecOpt,optChars,exOpts,  ArgsLeft,e) {
  1376.     if (MinArgs == "")
  1377.     MinArgs = 0
  1378.     ArgsLeft = ProcArgs(ARGC,ARGV,OptList NoRCopt,Options,1,AllowUnrecOpt,
  1379.     optChars)
  1380.     if (ArgsLeft < (MinArgs+1) && !("h" in Options)) {
  1381.     if (ArgsLeft >= 0) {
  1382.         OptErr = "Not enough arguments"
  1383.         Err = 4
  1384.     }
  1385.     else
  1386.         Err = -ArgsLeft
  1387.     printf "%s: %s.\nUse -h for help.\n%s\n",
  1388.     Name,OptErr,Usage > "/dev/stderr"
  1389.     exit 1
  1390.     }
  1391.     if (rcFiles != "" && (NoRCopt == "" || !(NoRCopt in Options)) &&
  1392.     (e = InitOpts(rcFiles,Options,OptList,VarNames,EnvSearch)) < 0)
  1393.     {
  1394.     print Name ": " OptErr ".\nUse -h for help." > "/dev/stderr"
  1395.     Err = -e
  1396.     exit 1
  1397.     }
  1398.     if ((exOpts != "") && ((OptErr = ExclusiveOptions(exOpts,Options)) != ""))
  1399.     {
  1400.     printf "%s: Error: %s\n",Name,OptErr > "/dev/stderr"
  1401.     Err = 1
  1402.     exit 1
  1403.     }
  1404.     return ArgsLeft
  1405. }
  1406.  
  1407. # ReadConfFile(): Read a file containing var/value assignments, in the form
  1408. # <variable-name><assignment-char><value>.
  1409. # Whitespace (spaces and tabs) around a variable (leading whitespace on the
  1410. # line and whitespace between the variable name and the assignment character) 
  1411. # is stripped.  Lines that do not contain an assignment operator or which
  1412. # contain a null variable name are ignored, other than possibly being noted in
  1413. # the return value.  If more than one assignment is made to a variable, the
  1414. # first assignment is used.
  1415. # Input variables:
  1416. # File is the file to read.
  1417. # Comment is the line-comment character.  If it is found as the first non-
  1418. #     whitespace character on a line, the line is ignored.
  1419. # Assign is the assignment string.  The first instance of Assign on a line
  1420. #     separates the variable name from its value.
  1421. # If StripWhite is true, whitespace around the value (whitespace between the
  1422. #     assignment char and trailing whitespace on the line) is stripped.
  1423. # VarPat is a pattern that variable names must match.  
  1424. #     Example: "^[a-zA-Z][a-zA-Z0-9]+$"
  1425. # If FlagsOK is true, variables are allowed to be "set" by being put alone on
  1426. #     a line; no assignment operator is needed.  These variables are set in
  1427. #     the output array with a null value.  Lines containing nothing but
  1428. #     whitespace are still ignored.
  1429. # Output variables:
  1430. # Values[] contains the assignments, with the indexes being the variable names
  1431. #     and the values being the assigned values.
  1432. # Lines[] contains the line number that each variable occured on.  A flag set
  1433. #     is record by giving it an index in Lines[] but not in Values[].
  1434. # Return value:
  1435. # If any errors occur, a string consisting of descriptions of the errors
  1436. # separated by newlines is returned.  In no case will the string start with a
  1437. # numeric value.  If no errors occur,  the number of lines read is returned.
  1438. function ReadConfigFile(Values,Lines,File,Comment,Assign,StripWhite,VarPat,
  1439. FlagsOK,
  1440. Line,Status,Errs,AssignLen,LineNum,Var,Val) {
  1441.     if (Comment != "")
  1442.     Comment = "^" Comment
  1443.     AssignLen = length(Assign)
  1444.     if (VarPat == "")
  1445.     VarPat = "."    # null varname not allowed
  1446.     while ((Status = (getline Line < File)) == 1) {
  1447.     LineNum++
  1448.     sub("^[ \t]+","",Line)
  1449.     if (Line == "")        # blank line
  1450.         continue
  1451.     if (Comment != "" && Line ~ Comment)
  1452.         continue
  1453.     if (Pos = index(Line,Assign)) {
  1454.         Var = substr(Line,1,Pos-1)
  1455.         Val = substr(Line,Pos+AssignLen)
  1456.         if (StripWhite) {
  1457.         sub("^[ \t]+","",Val)
  1458.         sub("[ \t]+$","",Val)
  1459.         }
  1460.     }
  1461.     else {
  1462.         Var = Line    # If no value, var is entire line
  1463.         Val = ""
  1464.     }
  1465.     if (!FlagsOK && Val == "") {
  1466.         Errs = Errs \
  1467.         sprintf("\nBad assignment on line %d of file %s: %s",
  1468.         LineNum,File,Line)
  1469.         continue
  1470.     }
  1471.     sub("[ \t]+$","",Var)
  1472.     if (Var !~ VarPat) {
  1473.         Errs = Errs sprintf("\nBad variable name on line %d of file %s: %s",
  1474.         LineNum,File,Var)
  1475.         continue
  1476.     }
  1477.     if (!(Var in Lines)) {
  1478.         Lines[Var] = LineNum
  1479.         if (Pos)
  1480.         Values[Var] = Val
  1481.     }
  1482.     }
  1483.     if (Status)
  1484.     Errs = Errs "\nCould not read file " File
  1485.     close(File)
  1486.     return Errs == "" ? LineNum : substr(Errs,2)    # Skip first newline
  1487. }
  1488.  
  1489. # Variables:
  1490. # Data is stored in Options[].
  1491. # rcFiles, OptList, VarNames, and EnvSearch are as as described for Opts().
  1492. # Global vars:
  1493. # Sets OptErr.  Uses ENVIRON[].
  1494. # If anything is read from any of the rcfiles, sets READ_RCFILE to 1.
  1495. function InitOpts(rcFiles,Options,OptList,VarNames,EnvSearch,
  1496. Line,Var,Pos,Vars,Map,CharOpt,NumVars,TypesInd,Types,Type,Ret,i,rcFile,
  1497. fNames,numrcFiles,filesRead,Err,Values,retStr) {
  1498.     split("",filesRead,"")    # make awk know this is an array
  1499.     NumVars = split(VarNames,Vars,",")
  1500.     TypesInd = Ret = 0
  1501.     if (EnvSearch == -1)
  1502.     EnvSearch = NumVars
  1503.     for (i = 1; i <= NumVars; i++) {
  1504.     Var = Vars[i]
  1505.     CharOpt = substr(OptList,++TypesInd,1)
  1506.     if (CharOpt ~ "^[:;*()#<>&]$")
  1507.         CharOpt = substr(OptList,++TypesInd,1)
  1508.     Map[Var] = CharOpt
  1509.     Types[Var] = Type = substr(OptList,TypesInd+1,1)
  1510.     # Do not overwrite entries from environment
  1511.     if (i <= EnvSearch && Var in ENVIRON &&
  1512.     (Err = AssignVal(CharOpt,ENVIRON[Var],Options,Type,1,Var,0)) < 0)
  1513.         return Err
  1514.     }
  1515.  
  1516.     numrcFiles = split(rcFiles,fNames,":")
  1517.     for (i = 1; i <= numrcFiles; i++) {
  1518.     rcFile = fNames[i]
  1519.     if (rcFile ~ "^~/")
  1520.         rcFile = ENVIRON["HOME"] substr(rcFile,2)
  1521.     else if (rcFile ~ /^\$/) {
  1522.         rcFile = substr(rcFile,2)
  1523.         match(rcFile,"^[a-zA-Z0-9_]*")
  1524.         envvar = substr(rcFile,1,RLENGTH)
  1525.         if (envvar in ENVIRON)
  1526.         rcFile = ENVIRON[envvar] substr(rcFile,RLENGTH+1)
  1527.         else
  1528.         continue
  1529.     }
  1530.     if (rcFile in filesRead)
  1531.         continue
  1532.     # rcfiles are liable to be given more than once, e.g. UHOME and HOME
  1533.     # may be the same
  1534.     filesRead[rcFile]
  1535.     if ("x" in Options)
  1536.         printf "Reading configuration file %s\n",rcFile > "/dev/stderr"
  1537.     retStr = ReadConfigFile(Values,Lines,rcFile,"#","=",0,"",1)
  1538.     if (retStr > 0)
  1539.         READ_RCFILE = 1
  1540.     else if (ret != "") {
  1541.         OptErr = retStr
  1542.         Ret = -1
  1543.     }
  1544.     for (Var in Lines)
  1545.         if (Var in Map) {
  1546.         if ((Err = AssignVal(Map[Var],
  1547.         Var in Values ? Values[Var] : "",Options,Types[Var],
  1548.         Var in Values,Var,0)) < 0)
  1549.             return Err
  1550.         }
  1551.         else {
  1552.         OptErr = sprintf(\
  1553.         "Unknown var \"%s\" assigned to on line %d\nof file %s",Var,
  1554.         Lines[Var],rcFile)
  1555.         Ret = -1
  1556.         }
  1557.     }
  1558.  
  1559.     if ("x" in Options)
  1560.     for (Var in Map)
  1561.         if (Map[Var] in Options)
  1562.         printf "(%s) %s=%s\n",Map[Var],Var,Options[Map[Var]] > \
  1563.         "/dev/stderr"
  1564.         else
  1565.         printf "(%s) %s not set\n",Map[Var],Var > "/dev/stderr"
  1566.     return Ret
  1567. }
  1568.  
  1569. # OptSets is a semicolon-separated list of sets of option sets.
  1570. # Within a list of option sets, the option sets are separated by commas.  For
  1571. # each set of sets, if any option in one of the sets is in Options[] AND any
  1572. # option in one of the other sets is in Options[], an error string is returned.
  1573. # If no conflicts are found, nothing is returned.
  1574. # Example: if OptSets = "ab,def,g;i,j", an error will be returned due to
  1575. # the exclusions presented by the first set of sets (ab,def,g) if:
  1576. # (a or b is in Options[]) AND (d, e, or f is in Options[]) OR
  1577. # (a or b is in Options[]) AND (g is in Options) OR
  1578. # (d, e, or f is in Options[]) AND (g is in Options)
  1579. # An error will be returned due to the exclusions presented by the second set
  1580. # of sets (i,j) if: (i is in Options[]) AND (j is in Options[]).
  1581. # todo: make options given on command line unset options given in config file
  1582. # todo: that they conflict with.
  1583. function ExclusiveOptions(OptSets,Options,
  1584. Sets,SetSet,NumSets,Pos1,Pos2,Len,s1,s2,c1,c2,ErrStr,L1,L2,SetSets,NumSetSets,
  1585. SetNum,OSetNum) {
  1586.     NumSetSets = split(OptSets,SetSets,";")
  1587.     # For each set of sets...
  1588.     for (SetSet = 1; SetSet <= NumSetSets; SetSet++) {
  1589.     # NumSets is the number of sets in this set of sets.
  1590.     NumSets = split(SetSets[SetSet],Sets,",")
  1591.     # For each set in a set of sets except the last...
  1592.     for (SetNum = 1; SetNum < NumSets; SetNum++) {
  1593.         s1 = Sets[SetNum]
  1594.         L1 = length(s1)
  1595.         for (Pos1 = 1; Pos1 <= L1; Pos1++)
  1596.         # If any of the options in this set was given, check whether
  1597.         # any of the options in the other sets was given.  Only check
  1598.         # later sets since earlier sets will have already been checked
  1599.         # against this set.
  1600.         if ((c1 = substr(s1,Pos1,1)) in Options)
  1601.             for (OSetNum = SetNum+1; OSetNum <= NumSets; OSetNum++) {
  1602.             s2 = Sets[OSetNum]
  1603.             L2 = length(s2)
  1604.             for (Pos2 = 1; Pos2 <= L2; Pos2++)
  1605.                 if ((c2 = substr(s2,Pos2,1)) in Options)
  1606.                 ErrStr = ErrStr "\n"\
  1607.                 sprintf("Cannot give both %s and %s options.",
  1608.                 c1,c2)
  1609.             }
  1610.     }
  1611.     }
  1612.     if (ErrStr != "")
  1613.     return substr(ErrStr,2)
  1614.     return ""
  1615. }
  1616.  
  1617. # The value of each instance of option Opt that occurs in Options[] is made an
  1618. # index of Set[].
  1619. # The return value is the number of instances of Opt in Options.
  1620. function Opt2Set(Options,Opt,Set,  count) {
  1621.     if (!(Opt in Options))
  1622.     return 0
  1623.     Set[Options[Opt]]
  1624.     count = Options[Opt,"count"]
  1625.     for (; count > 1; count--)
  1626.     Set[Options[Opt,count]]
  1627.     return count
  1628. }
  1629.  
  1630. # The value of each instance of option Opt that occurs in Options[] that
  1631. # begins with "!" is made an index of nSet[] (with the ! stripped from it).
  1632. # Other values are made indexes of Set[].
  1633. # The return value is the number of instances of Opt in Options.
  1634. function Opt2Sets(Options,Opt,Set,nSet,  count,aSet,ret) {
  1635.     ret = Opt2Set(Options,Opt,aSet)
  1636.     for (value in aSet)
  1637.     if (substr(value,1,1) == "!")
  1638.         nSet[substr(value,2)]
  1639.     else
  1640.         Set[value]
  1641.     return ret
  1642. }
  1643.  
  1644. # Returns true if option Opt was given on the command line.
  1645. function CmdLineOpt(Options,Opt,  i) {
  1646.     for (i = 1; (Opt,"num",i) in Options; i++)
  1647.     if (Options[Opt,"num",i] != 0)
  1648.         return 1
  1649.     return 0
  1650. }
  1651. ### End of ProcArgs library
  1652. # @(#) CmdReadLine 95/09/04
  1653. # Run Command, read a single line of output from it, then close it.
  1654. # If Verbose is true, a complaint is issued if the read fails.
  1655. # Output is returned in $*
  1656. # The return value from getline is returned.  It will be 1 on a successful
  1657. # read; 0 if no lines were read due because the command produced no output
  1658. # or could not be run.  ERRNO is never set since pipes are run by a shell.
  1659. function CmdReadLine(Command,Verbose,  ret) {
  1660.     if (Debug) {
  1661.     print "* Issuing command: " Command "\n"\
  1662.           "* Waiting for single line of output..." > "/dev/stderr"
  1663.     }
  1664.     ret = Command | getline
  1665.     if (Verbose && ret != 1)
  1666.     printf "Read from pipe \"%s\" failed\n",Command
  1667.     # close does not return a value under awk, only gawk
  1668.     close(Command)
  1669.     if (Debug)
  1670.     print "* Output: " $0 > "/dev/stderr"
  1671.     return ret
  1672. }
  1673. ### Begin qsort routines
  1674.  
  1675. # Arr[] is an array of values with arbitrary indices.
  1676. # k[] is returned with numeric indices 1..n.
  1677. # The values in k[] are the indices of Arr[],
  1678. # ordered so that if Arr[] is stepped through
  1679. # in the order Arr[k[1]] .. Arr[k[n]], it will be stepped
  1680. # through in order of the values of its elements.
  1681. # The return value is the number of elements in the arrays (n).
  1682. function qsortArbIndByValue(Arr,k,  ArrInd,ElNum) {
  1683.     ElNum = 0
  1684.     for (ArrInd in Arr)
  1685.     k[++ElNum] = ArrInd
  1686.     qsortSegment(Arr,k,1,ElNum)
  1687.     return ElNum
  1688. }
  1689.  
  1690. # Sort a segment of an array.
  1691. # Arr[] contains data with arbitrary indices.
  1692. # k[] has indices 1..nelem, with the indices of arr[] as values.
  1693. # This function sorts the elements of arr that are pointed to by
  1694. # k[start..end], swapping the values of elements of k[] so that
  1695. # when this function returns arr[k[start..end]] will be in order.
  1696. function qsortSegment(Arr,k,start,end,  left,right,sepval,tmp,tmpe,tmps) {
  1697.     # handle two-element case explicitly for a tiny speedup
  1698.     if ((end - start) == 1) {
  1699.     if (Arr[tmps = k[start]] > Arr[tmpe = k[end]]) {
  1700.         k[start] = tmpe
  1701.         k[end] = tmps
  1702.     }
  1703.     return
  1704.     }
  1705.     # Make sure comparisons act on these as numbers
  1706.     left = start+0
  1707.     right = end+0
  1708.     sepval = Arr[k[int((left + right) / 2)]]
  1709.     # Make every element <= sepval be to the left of every element > sepval
  1710.     while (left < right) {
  1711.     while (Arr[k[left]] < sepval)
  1712.         left++
  1713.     while (Arr[k[right]] > sepval)
  1714.         right--
  1715.     if (left < right) {
  1716.         tmp = k[left]
  1717.         k[left++] = k[right]
  1718.         k[right--] = tmp
  1719.     }
  1720.     }
  1721.     if (left == right)
  1722.     if (Arr[k[left]] < sepval)
  1723.         left++
  1724.     else
  1725.         right--
  1726.     if (start < right)
  1727.     qsortSegment(Arr,k,start,right)
  1728.     if (left < end)
  1729.     qsortSegment(Arr,k,left,end)
  1730. }
  1731.  
  1732. # Arr[] is an array of values with arbitrary indices.
  1733. # k[] is returned with numeric indices 1..n.
  1734. # The values in k are the indices of Arr[],
  1735. # ordered so that if Arr[] is stepped through
  1736. # in the order Arr[k[1]] .. Arr[k[n]], it will be stepped
  1737. # through in order of the values of its indices.
  1738. # The return value is the number of elements in the arrays (n).
  1739. # If the indexes are numeric, Numeric should be true, so that they can be
  1740. # compared as such rather than as strings.  Numeric indexes do not have to be
  1741. # contiguous.
  1742. function qsortByArbIndex(Arr,k,Numeric,  ArrInd,ElNum) {
  1743.     ElNum = 0
  1744.     if (Numeric)
  1745.     # Indexes do not preserve numeric type, so must be forced
  1746.     for (ArrInd in Arr)
  1747.         k[++ElNum] = ArrInd+0
  1748.     else
  1749.     for (ArrInd in Arr)
  1750.         k[++ElNum] = ArrInd
  1751.     qsortNumIndByValue(k,1,ElNum)
  1752.     return ElNum
  1753. }
  1754.  
  1755. # Arr is an array of elements with contiguous numeric indexes to be sorted
  1756. # by value.
  1757. # start and end are the starting and ending indexes of the range to be sorted.
  1758. function qsortNumIndByValue(Arr,start,end,  left,right,sepval,tmp,tmpe,tmps) {
  1759.     # handle two-element case explicitly for a tiny speedup
  1760.     if ((start - end) == 1) {
  1761.     if ((tmps = Arr[start]) > (tmpe = Arr[end])) {
  1762.         Arr[start] = tmpe
  1763.         Arr[end] = tmps
  1764.     }
  1765.     return
  1766.     }
  1767.     left = start+0
  1768.     right = end+0
  1769.     sepval = Arr[int((left + right) / 2)]
  1770.     while (left < right) {
  1771.     while (Arr[left] < sepval)
  1772.         left++
  1773.     while (Arr[right] > sepval)
  1774.         right--
  1775.     if (left <= right) {
  1776.         tmp = Arr[left]
  1777.         Arr[left++] = Arr[right]
  1778.         Arr[right--] = tmp
  1779.     }
  1780.     }
  1781.     if (start < right)
  1782.     qsortNumIndByValue(Arr,start,right)
  1783.     if (left < end)
  1784.     qsortNumIndByValue(Arr,left,end)
  1785. }
  1786.  
  1787. ### End qsort routines
  1788. ### Begin pwent library
  1789.  
  1790. # @(#) pwent.awk 1.3 96/12/15
  1791. # 92/08/10 john h. dubois III (john@armory.com)
  1792. # 93/12/13 fixed to not clobber $*
  1793. # 96/01/05 Send error messages to /dev/stderr
  1794. # 96/05/24 Let getpwnam() return a specific field if requested.
  1795. #          Added PW_REAL and PW_OFFICE.
  1796. # 96/06/03 Added Type field to getpwent()
  1797. # 96/06/24 Allow a Field to be requested for getpwent() also.
  1798. # 96/06/29 Added PW_RECORD, and getpwreal().
  1799. #          Changed PWLines to be index by record number instead of name.
  1800. # 96/11/17 Added getpwuid()
  1801. # 96/12/15 Added pwFieldNames, and pwent() as general select-by-field routine.
  1802.  
  1803. # Require: ReadShells()
  1804.  
  1805. # getpwent, getpwnam: get an entry from the passwd file.
  1806. # Each of the following passwd functions returns an array which contains
  1807. # a passwd file entry.  The array contains the fields of the entry.
  1808. # Global variables:
  1809. # The following variables are defined with the values of the indexes of the
  1810. # entries: PW_NAME, PW_PASSWORD, PW_UID, PW_GID, PW_GCOS, PW_HOME, PW_SHELL
  1811. # PWLines[] contains the lines of the password file, indexed by record number,
  1812. # starting with 1.
  1813. # _pwNames[] is a mapping of name to passwd record number.
  1814. # pwFieldNames[field-index] is set to a short description of each field.
  1815. # getpwentNum is the number of the next entry to be returned by getpwent().
  1816.  
  1817. # Left FS global because making it local does not work in gawk.
  1818. function ReadPasswd(  User,Line,i,Ind,ret) {
  1819.     if (PW_Name)
  1820.     return 1
  1821.     PW_NAME = 1
  1822.     PW_PASSWORD = 2
  1823.     PW_UID = 3
  1824.     PW_GID = 4
  1825.     PW_GCOS = 5
  1826.     PW_HOME = 6
  1827.     PW_SHELL = 7
  1828.     PW_REAL = -1    # for PWGetFields()
  1829.     PW_OFFICE = -2
  1830.     PW_RECORD = -3
  1831.  
  1832.     split(\
  1833.     "user name,password,UID,login GID,GCOS,home directory,login shell",
  1834.     pwFieldNames,",")
  1835.     pwFieldNames[-1] = "real name"
  1836.     pwFieldNames[-2] = "office"
  1837.     pwFieldNames[-3] = "record number"
  1838.     getpwentNum = 1
  1839.     while ((ret = (getline Line < "/etc/passwd")) == 1) {
  1840.     PWLines[++Ind] = Line
  1841.     _pwNames[substr(Line,1,index(Line,":")-1)] = Ind
  1842.     }
  1843.     _num_pw = Ind
  1844.     close("/etc/passwd")
  1845.     if (ret) {
  1846.     printf "ReadPasswd(): Could not open /etc/passwd: %s\n",
  1847.     ERRNO > "/dev/stderr"
  1848.     return 0
  1849.     }
  1850.     return 1
  1851. }
  1852.  
  1853. # setpwent resets the passwd file entry pointer used by getpwent
  1854. # to the first entry.
  1855. function setpwent() {
  1856.     getpwentNum = 1
  1857. }
  1858.  
  1859. # getpwent sets PWEnt to the next entry in the passwd file.
  1860. # If Type is set to -1, the entry for the next "real" user is returned (others
  1861. # are skipped over), where a real user is a user whose login shell is listed in
  1862. # /etc/shells.  This requires the ReadShells() function.  Other values for
  1863. # Type are not yet defined and are ignored.
  1864. # If the last entry has already been returned, 0 is return if Field is null,
  1865. # ":" if not.
  1866. # If the entry for the next real user has been requested and /etc/shells
  1867. # cannot be read, -1 is returned if Field is null, "\n" if not.
  1868. # See PWGetFields() for other return values and the meaning of the Field
  1869. # parameter.
  1870. function getpwent(PWEnt,Type,Field,  entNum) {
  1871.     if (!PW_NAME)
  1872.     ReadPasswd()
  1873.     if (!(getpwentNum in PWLines))
  1874.     return Field ? ":" : 0
  1875.     if (Type == -1) {
  1876.     if (!_DidReadShells && ReadShells(LoginShells) == -1)
  1877.         return Field ? "\n" : -1
  1878.     split(PWLines[getpwentNum++],PWEnt,":")
  1879.     while (!(PWEnt[PW_SHELL] in LoginShells)) {
  1880.         if (!(getpwentNum in PWLines))
  1881.         return Field ? ":" : 0
  1882.         split(PWLines[getpwentNum++],PWEnt,":")
  1883.     }
  1884.     return PWGetFields("",PWEnt,Field,getpwentNum - 1)
  1885.     }
  1886.     else {
  1887.     entNum = getpwentNum
  1888.     return PWGetFields(PWLines[getpwentNum++],PWEnt,Field,entNum)
  1889.     }
  1890. }
  1891.  
  1892. # PWGetFields() splits PWLine into PWEnt[], and optionally returns a field
  1893. # from it.  If PWLine is null, PWEnt[] is assumed to have already been filled
  1894. # in with a password entry.
  1895. # If Field is not passed or is null, the return value is 1.
  1896. # If Field is non-null, it should a PW_ value.  In this case, the value of the
  1897. # requested field is returned.
  1898. # If entNum is nonzero, it is the value that PWEnt[PW_RECORD] should be set to.
  1899. # It should be the index in PWLines[] of the record being processed.
  1900. # In addition to the PW_ values used by the rest of the functions in this
  1901. # library, this function can be passed PW_REAL and PW_OFFICE.
  1902. # PW_REAL will get the part of the GCOS field before the first comma.
  1903. # PW_OFFICE will get the part of the GCOS field after the first comma.
  1904. # If either of these is requested, both values will also be assigned to their
  1905. # indices in PWEnt[], unless there is no comma in the GCOS field, in which case
  1906. # PW_OFFICE will not be set.
  1907. # NOTE: since the global field names are set in ReadShells(), it must be
  1908. # executed before any of the field name can be passed.
  1909. function PWGetFields(PWLine,PWEnt,Field,entNum,  gcos,ind) {
  1910.     if (PWLine != "")
  1911.     split(PWLine,PWEnt,":")
  1912.     if (entNum)
  1913.     PWEnt[PW_RECORD] = entNum
  1914.     if (!Field)
  1915.     return 1
  1916.     if (Field < 0) {
  1917.     if (ind = index(gcos = PWEnt[PW_GCOS],",")) {
  1918.         PWEnt[PW_OFFICE] = substr(gcos,ind+1)
  1919.         PWEnt[PW_REAL] = substr(gcos,1,ind-1)
  1920.     }
  1921.     else
  1922.         PWEnt[PW_REAL] = gcos
  1923.     }
  1924.     return PWEnt[Field]
  1925. }
  1926.  
  1927. # getpwnam sets PWEnt to the passwd entry for login name Name.
  1928. # If Next is true or Name does not exist in the password file, the return value
  1929. # is ":" if Field was passed, 0 if not.
  1930. # For other return values and parameter explanation, see PWGetFields()
  1931. function getpwnam(Name,PWEnt,Field,Next) {
  1932.     if (!PW_NAME)
  1933.     ReadPasswd()
  1934.     if (!Next && Name in _pwNames)
  1935.     return PWGetFields(PWLines[_pwNames[Name]],PWEnt,Field,_pwNames[Name])
  1936.     else
  1937.     return Field ? ":" : 0
  1938. }
  1939.  
  1940. # Build uid->pw-index and home->pw-index
  1941. # Globals: _pwIndexes[]
  1942. function MakeInd(  Elem,Ind,Line,i,Fields,f) {
  1943.     Fields[PW_UID]
  1944.     Fields[PW_HOME]
  1945.     Fields[PW_GID]
  1946.     Fields[PW_SHELL]
  1947.     for (Ind = 1; Ind in PWLines; Ind++) {
  1948.     Line = PWLines[Ind]
  1949.     split(Line,Elem,":")
  1950.     for (f in Fields) {
  1951.         i = f ":" Elem[f]
  1952.         if (i in _pwIndexes)
  1953.         _pwIndexes[i] = _pwIndexes[i] "," Ind
  1954.         else
  1955.         _pwIndexes[i] = Ind
  1956.     }
  1957.     }
  1958.     IndDone = 1
  1959. }
  1960.  
  1961. # getpw sets PWEnt to the first passwd entry whose FieldNum'th field is equal
  1962. # to Value.  FieldNum may select any field except the password field.
  1963. # If Next is true, Value is ignored and the next entry in the password file
  1964. # that has the same FieldNum field as the last one requested is returned.
  1965. # See getpwnam() for return values and the meaning of the Field param.
  1966. # IgnoreCase and Full are used only for PW_GCOS searches; see getpwreal()
  1967. # for their meaning.
  1968. # Uses globals _pwMatches[] to track matches between calls.
  1969. function getpw(FieldNum,Value,PWEnt,Field,Next,IgnoreCase,Full,  elem,i,n) {
  1970.     if (FieldNum == PW_GCOS)
  1971.     return getpwreal(Value,PWEnt,Field,Next,IgnoreCase,Full)
  1972.     if (FieldNum == PW_NAME)
  1973.     return getpwnam(Value,PWEnt,Field,Next)
  1974.     if (Next) {
  1975.     if ((FieldNum,_pwMatches[FieldNum,"cur"]) in _pwMatches) {
  1976.         i = _pwMatches[FieldNum,_pwMatches[FieldNum,"cur"]++]
  1977.         return PWGetFields(PWLines[i],PWEnt,Field,i)
  1978.     }
  1979.     }
  1980.     else {
  1981.     if (!IndDone)
  1982.         MakeInd()
  1983.     Value = FieldNum ":" Value
  1984.     if (Value in _pwIndexes) {
  1985.         n = split(_pwIndexes[Value],elem,",")
  1986.         for (i = 1; i <= n; i++)
  1987.         _pwMatches[FieldNum,i] = elem[i]
  1988.         delete _pwMatches[FieldNum,n+1]    # so we know when to end
  1989.         _pwMatches[FieldNum,"cur"] = 2
  1990.         return PWGetFields(PWLines[elem[1]],PWEnt,Field,elem[1])
  1991.     }
  1992.     }
  1993.     return Field ? ":" : 0
  1994. }
  1995.  
  1996. function getpwhome(home,PWEnt,Field,Next) {
  1997.     if (!PW_NAME)    # set PW_ values
  1998.     ReadPasswd()
  1999.     return getpw(PW_HOME,home,PWEnt,Field,Next)
  2000. }
  2001.  
  2002. function getpwuid(UID,PWEnt,Field,Next) {
  2003.     if (!PW_NAME)    # set PW_ values
  2004.     ReadPasswd()
  2005.     return getpw(PW_UID,(UID+0),PWEnt,Field,Next)
  2006. }
  2007.  
  2008. # Make an index by real name.  For each passwd file entry, the real-name
  2009. # is lowercased and split into components on non-alphanums.   The passwd entry
  2010. # index that the name came from is added to the value of each such component
  2011. # in the global _RealInd[].  The indexes stored this way are separated by
  2012. # commas.  If the real-name contains no alphanums, its index is stored under
  2013. # the null index.
  2014. function _makeRealInd(  PWEnt,ret,Elem,nelem,i,Component) {
  2015.     setpwent()
  2016.     while ((ret = getpwent(PWEnt,"",PW_REAL)) != ":") {
  2017.     nelem = split(tolower(ret),Elem,/[^a-z0-9]+/)
  2018.     for (i = 1; i <= nelem; i++) {
  2019.         Component = Elem[i]
  2020.         if (Component == "" && nelem > 1)
  2021.         continue
  2022.         if (Component in _RealInd)
  2023.         _RealInd[Component] = _RealInd[Component] "," PWEnt[PW_RECORD]
  2024.         else
  2025.         _RealInd[Component] = PWEnt[PW_RECORD]
  2026.     }
  2027.     }
  2028.     _realIndDone = 1
  2029. }
  2030.  
  2031. # Make Name into a pattern that will match a name that contains all of the
  2032. # same name components (sequences of alphanums) in the same order.  If Name
  2033. # contains no name components, a null string is returned.
  2034. function MakeNamePat(Name,  Elem,nelem,i,Pat,e) {
  2035.     nelem = split(Name,Elem,/[^a-zA-Z0-9]+/)
  2036.     for (i = 1; i <= nelem; i++) {
  2037.     if ((e = Elem[i]) == "")
  2038.         continue
  2039.     if (Pat == "")
  2040.         Pat = "(^|[^a-zA-Z0-9])" e
  2041.     else
  2042.         Pat = Pat "[^a-zA-Z0-9](.*[^a-zA-Z0-9])?" e
  2043.     }
  2044.     if (Pat == "")    # If Name contained no alphanums...
  2045.     return ""
  2046.     Pat = Pat "([^a-zA-Z0-9]|$)"
  2047.     return Pat
  2048. }
  2049.  
  2050. # getpwgreal sets PWEnt to the first passwd entry whose PW_REAL (see
  2051. # PWGetFields()) field matches Real.  Matching occurs if the alphanumeric
  2052. # components of Real occur in the same order in the entry.  Non-alphanums are
  2053. # ignored.  All of the components in Real must occur in the entry, but not all
  2054. # of the components in the entry must occur in Real.
  2055. # If the given name does not exist in the password file,
  2056. # the return value is ":" if Field was passed, 0 if not.
  2057. # If Next is true, getpwreal() sets PWEnt to the next passwd entry whose
  2058. # PW_REAL field matches the last previous Real parameter passed.
  2059. # In this case,  if the last entry has already been returned,
  2060. # the return value is ":" if Field was passed, 0 if not.
  2061. # Different IgnoreCase and Full parameters may be given when doing a Next
  2062. # search.  Both must always be passed; they do not default to the original
  2063. # values when doing a Next search.  The only parameter ignored when doing a
  2064. # Next search is Real.
  2065. # If IgnoreCase is true, case is ignored when searching.
  2066. # If Full is true, a match of the full name is required (including any
  2067. # punctuation).
  2068. # For successful return values and Field parameter explanation,
  2069. # see PWGetFields()
  2070. # Globals: For the Next search, between invokations these varies store values:
  2071. # _getpwrealInd[]: The set of pw indices that matched the query.
  2072. # _getpwrealIndInd: The next index in _getpwrealInd[] to look at.
  2073. # _getpwrealReal: The Real value passed with the original query.
  2074. # _getpwrealPat: Real converted to a component order search pattern.
  2075. function getpwreal(Real,PWEnt,Field,Next,IgnoreCase,Full,  ind,name,Pat) {
  2076.     if (!Next) {
  2077.     if (!PW_NAME)
  2078.         ReadPasswd()
  2079.     if (!_realIndDone)
  2080.         _makeRealInd()
  2081.     _getpwrealReal = Real
  2082.     _getpwrealPat = MakeNamePat(Real)
  2083.     # Get first component from Real
  2084.     Real = tolower(Real)
  2085.     gsub("^[^a-z0-9]+","",Real)
  2086.     gsub("[^a-z0-9].*","",Real)
  2087.     if (!(Real in _RealInd))
  2088.         return Field ? ":" : 0
  2089.     split(_RealInd[Real],_getpwrealInd,",")
  2090.     _getpwrealIndInd = 1
  2091.     }
  2092.     if (Full)
  2093.     Pat = _getpwrealReal
  2094.     else
  2095.     Pat = _getpwrealPat
  2096.     if (IgnoreCase)
  2097.     Pat = tolower(Pat)
  2098.     while (_getpwrealIndInd in _getpwrealInd) {
  2099.     ind = _getpwrealInd[_getpwrealIndInd++]
  2100.     name = PWGetFields(PWLines[ind],PWEnt,PW_REAL,ind)
  2101.     if (IgnoreCase)
  2102.         name = tolower(name)
  2103.     if (Full ? (name == Pat) : (name ~ Pat))
  2104.         return PWGetFields("",PWEnt,Field,ind)
  2105.     }
  2106.     return Field ? ":" : 0
  2107. }
  2108.  
  2109. ### End pwent library
  2110. ### Begin min,max,In routines
  2111.  
  2112. function min(a,b) {
  2113.     if (a < b)
  2114.     return a
  2115.     else
  2116.     return b
  2117. }
  2118.  
  2119. function max(a,b) {
  2120.     if (a > b)
  2121.     return a
  2122.     else
  2123.     return b
  2124. }
  2125.  
  2126. function In(Val,Min,Max) {
  2127.     return (Min <= Val && Val <= Max)
  2128. }
  2129.  
  2130. # Return (in Ind) the indices of the elements with the smallest value in A.
  2131. # The smallest value is returned as the function value.
  2132. # If there are no elements in A, null is returned.
  2133. function arrMin(A,Ind,  i,min) {
  2134.     for (i in A)
  2135.     if (min == "" || A[i] < min) {
  2136.         DeleteAll(Ind)
  2137.         min = A[i]
  2138.         Ind[i]
  2139.     }
  2140.     else if (A[i] == min)
  2141.         Ind[i]
  2142.     return min
  2143. }
  2144.  
  2145. ### End min,max,In routines
  2146.